/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the EternalTech source code.

 The EternalTech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The EternalTech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the EternalTech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// sg_entity.cpp - entity management
//


#include "sg_local.h"


/*
 ==============================================================================

 ENTITY SETUP

 ==============================================================================
*/


/*
 ==================
 
 FIXME: not sure if Quake uses more keywords for angles/rotation
 ==================
*/
void etEntity::Setup (int number, int type, const char *name, const char *className, const etDict *dict){

	inUse = true;

	// Set the name
	SetName(name);

	// Set the class name
	SetClassName(className);

	// Copy the dictionary if possible
	if (dict)
		spawnDict = *dict;
	else
		spawnDict.Clear();

	// Set the current position and orientation
	currentOrigin = spawnDict.GetVec3("origin");

	currentAngles[0] = 0.0f;
	currentAngles[1] = glqMath::AngleNormalize360(spawnDict.GetFloat("angle"));
	currentAngles[2] = 0.0f;

	currentAxis = currentAngles.ToMat3();

	// Precache the assets

	// Set the clip mask

	// Set the clip model
	SetClipModel(NULL, 0, glqBounds(-8.0f, 8.0f));

	// Set the absolute bounds
	if (currentAngles.IsCleared())
		absBounds = bounds + currentOrigin;
	else
		absBounds = bounds.Transform(currentOrigin, currentAxis);

	// Set the client if needed
	if (number < MAX_CLIENTS)
		client = &level.clients[number];

	// Set up the entity state
	state.number = number;
	state.type = type;
	state.flags = 0;

	state.origin.Init(TR_STATIONARY, 0, 0, currentOrigin, vec3_origin);
	state.angles.Init(TR_STATIONARY, 0, 0, currentAngles, angles_zero);

	state.bindMaster = ENTITYNUM_NONE;
	state.bindOrientated = false;
}

/*
 ==================
 
 ==================
*/
void etEntity::Clear (){

	if (!inUse)
		return;

	// Unlink from world
	SG_UnlinkEntity(this);

	// Remove from hash table
	SG_RemoveEntityFromHashTable(this);

	// TODO: add more if needed

	// Unbind from master
	Unbind();

	// Quit physics team
	QuitTeam();

	// Quit mover team
	QuitMoverTeam();

	// Clear the dictionary
	spawnDict.Clear();

	// Free the name and class name
	if (name)
		glqMemory->Free(name);
	if (className)
		glqMemory->Free(className);

	// Free the physics rigid bodies
	if (physics)
		glqMemory->Free(physics);

	// Clear the entity
	glqMemory->Fill(this, 0, sizeof(etEntity));
}

/*
 ==================
 
 ==================
*/
int etEntity::Allocated () const {

	int		bytes = 0;

	if (name)
		bytes += glqMemory->Size(name);
	if (className)
		bytes += glqMemory->Size(className);

	if (physics)
		bytes += glqMemory->Size(physics);

	// TODO: add more if needed

	return bytes;
}

/*
 ==================
 etEntity::SetName
 ==================
*/
void etEntity::SetName (const char *newName){

	// Check if it didn't change at all
	if (name){
		if (!Str_Compare(name, newName))
			return;

		// Remove from hash table
		SG_RemoveEntityFromHashTable(this);

		// Free the old name
		glqMemory->Free(name);
	}

	// Set the new name
	name = glqMemory->DupString(newName, TAG_SERVER);

	// Add to hash table
	SG_AddEntityToHashTable(this);
}

/*
 ==================
 etEntity::SetClassName
 ==================
*/
void etEntity::SetClassName (const char *newClassName){

	// Check if it didn't change at all
	if (className){
		if (!Str_Compare(className, newClassName))
			return;

		// Free the old class name
		glqMemory->Free(className);
	}

	// Set the new class name
	className = glqMemory->DupString(newClassName, TAG_SERVER);
}


/*
 ==============================================================================

 PARSING

 ==============================================================================
*/


/*
 ==================

 ==================
*/
void etEntity::ParseGeneralState (){

	general_t	*general = (general_t *)state.buffer;
	const char	*name;

	state.type = ET_GENERAL;

	// Get the bounds (only needed if no clip model is specified)

	// Get the model
	name = spawnDict.GetString("className");

	if (!Str_ICompare(name, "light_torch_small_walltorch"))
		general->model = SG_ModelIndex("progs/flame.mdl");
	else if (!Str_ICompare(name, "light_flame_large_yellow") || !Str_ICompare(name, "light_flame_small_yellow") || !Str_ICompare(name, "light_flame_small_white"))
		general->model = SG_ModelIndex("progs/flame2.mdl");
}

/*
 ==================

 FIXME: inline models has the name for example: "*1", we only need the number, then set the name to _inlineModel%i
 ==================
*/
void etEntity::ParseMoverState (){

	mover_t		*mover = (mover_t *)state.buffer;
	const char	*name;
	char		realName[MAX_PATH_LENGTH];

	state.type = ET_MOVER;

	// Get the model
	name = spawnDict.GetString("model");
	Str_SPrintf(realName, sizeof(realName), "_inlineModel%s", name);

	mover->model = SG_ModelIndex(realName);
}

/*
 ==================
 etEntity::ParseLightState
 ==================
*/
void etEntity::ParseLightState (){

	light_t		*light = (light_t *)state.buffer;
	const char	*name;
	glqVec3		size;
	glqBounds	bounds;

	state.type = ET_LIGHT;

	// Parse the material parms
	ParseMaterialParms(light->materialParms);

	// Get the type
	light->type = spawnDict.GetInteger("type");
	light->type = glqMath::ClampInt(light->type, 0, 3);

	// Get the center
	light->center = spawnDict.GetVec3("center");

	if (light->type == RL_DIRECTIONAL && light->center.IsCleared())
		light->center.Set(0.0f, 0.0f, 1.0f);

	// Get the radius
	light->radius = spawnDict.GetVec3("radius", "100 100 100");

	light->radius[0] = Max(light->radius[0], 1.0f);
	light->radius[1] = Max(light->radius[1], 1.0f);
	light->radius[2] = Max(light->radius[2], 1.0f);

	// Get the frustum
	light->xMin = spawnDict.GetFloat("xMin", "-0.5");
	light->xMax = spawnDict.GetFloat("xMax", "0.5");

	if (light->xMin >= light->xMax){
		light->xMin = -0.5f;
		light->xMax = 0.5f;
	}

	light->yMin = spawnDict.GetFloat("yMin", "-0.5");
	light->yMax = spawnDict.GetFloat("yMax", "0.5");

	if (light->yMin >= light->yMax){
		light->yMin = -0.5f;
		light->yMax = 0.5f;
	}

	light->zNear = spawnDict.GetFloat("zNear", "1");
	light->zNear = Max(light->zNear, 1.0f);

	light->zFar = spawnDict.GetFloat("zFar", "200");
	light->zFar = Max(light->zFar, 2.0f);

	if (light->zNear >= light->zFar){
		light->zNear = 1.0f;
		light->zFar = 200.0f;
	}

	// Get the shadowing parameters
	light->noShadows = spawnDict.GetBool("noShadows");

	light->shadowBias = spawnDict.GetFloat("shadowBias", "1");
	light->shadowBias = Max(light->shadowBias, 0.0f);

	light->shadowSlopeScaleBias = spawnDict.GetFloat("shadowSlopeScaleBias", "1");
	light->shadowSlopeScaleBias = Max(light->shadowSlopeScaleBias, 0.0f);

	light->shadowSoftness = spawnDict.GetFloat("shadowSoftness", "0.5");
	light->shadowSoftness = glqMath::ClampFloat(light->shadowSoftness, 0.0f, 1.0f);

	// Get the fogging parameters
	light->fogDistance = spawnDict.GetFloat("fogDistance", "500");
	light->fogDistance = Max(light->fogDistance, 1.0f);

	light->fogHeight = spawnDict.GetFloat("fogHeight", "500");
	light->fogHeight = Max(light->fogHeight, 1.0f);

	// Get the volumetric lighting parameters
	light->volumeIntensity = spawnDict.GetFloat("volumeIntensity");
	light->volumeIntensity = Max(light->volumeIntensity, 0.0f);

	light->volumeShadows = spawnDict.GetBool("volumeShadows", "1");
	light->volumeInteractions = spawnDict.GetBool("volumeInteractions", "1");

	// Get the attributes
	light->detailLevel = spawnDict.GetInteger("detailLevel");
	light->detailLevel = glqMath::ClampInt(light->detailLevel, 0, 2);

	// Get the material
	name = spawnDict.GetString("material");
	light->material = SG_MaterialIndex(name);
}

/*
 ==================
 etEntity::ParseSpeakerState
 ==================
*/
void etEntity::ParseSpeakerState (){

	speaker_t	*speaker = (speaker_t *)state.buffer;

	state.type = ET_SPEAKER;

	// Parse the sound parms
	ParseSoundParms(speaker->soundParms);

	// Get the direction
	speaker->direction = spawnDict.GetVec3("direction");
	speaker->direction.Normalize();
}

/*
 ==================

 FIXME: the bounds needs to be set for each item entity, for now the bounds are just temporary

 FIXME: health items uses spawn flags (H_ROTTEN, H_MEGA, default) for them in order to load properly, so
 fix their models later
 ==================
*/
void etEntity::ParseItemState (){

	item_t		*item = (item_t *)state.buffer;
	const char	*name;

	state.type = ET_ITEM;

	// Get the bounds
	bounds.Set(-8.0f, -8.0f, 0.0f, 8.0f, 8.0f, 16.0f);

	item->bounds = G_EncodeBounds(bounds);

	// Set the model
	name = spawnDict.GetString("className");

	if (!Str_ICompare(name, "item_health"))
		item->model = SG_ModelIndex("maps/b_bh10.bsp");
	else if ((!Str_ICompare(name, "item_armor1")) || (!Str_ICompare(name, "item_armor2")) || (!Str_ICompare(name, "item_armorinv")))
		item->model = SG_ModelIndex("progs/armor.mdl");
	else if (!Str_ICompare(name, "item_shells"))
		item->model = SG_ModelIndex("maps/b_shell1.bsp");
	else if (!Str_ICompare(name, "item_spikes"))
		item->model = SG_ModelIndex("maps/b_nail1.bsp");
	else if (!Str_ICompare(name, "item_rockets"))
		item->model = SG_ModelIndex("maps/b_rock1.bsp");
	else if (!Str_ICompare(name, "item_cells"))
		item->model = SG_ModelIndex("maps/b_batt1.bsp");
	else if (!Str_ICompare(name, "item_key1"))
		item->model = SG_ModelIndex("progs/w_s_key.mdl");
	else if (!Str_ICompare(name, "item_key2"))
		item->model = SG_ModelIndex("progs/w_g_key.mdl");
	else if (!Str_ICompare(name, "item_sigil"))
		item->model = SG_ModelIndex("progs/end1.mdl");
	else if (!Str_ICompare(name, "item_artifact_invulnerability"))
		item->model = SG_ModelIndex("progs/invulner.mdl");
	else if (!Str_ICompare(name, "item_artifact_envirosuit"))
		item->model = SG_ModelIndex("progs/suit.mdl");
	else if (!Str_ICompare(name, "item_artifact_invisibility"))
		item->model = SG_ModelIndex("progs/invisibl.mdl");
	else if (!Str_ICompare(name, "item_artifact_super_damage"))
		item->model = SG_ModelIndex("progs/quaddama.mdl");
	else if (!Str_ICompare(name, "weapon_supershotgun"))
		item->model = SG_ModelIndex("progs/g_shot2.mdl");
	else if (!Str_ICompare(name, "weapon_nailgun"))
		item->model = SG_ModelIndex("progs/g_nail.mdl");
	else if (!Str_ICompare(name, "weapon_supernailgun"))
		item->model = SG_ModelIndex("progs/v_nail2.mdl");
	else if (!Str_ICompare(name, "weapon_grenadelauncher"))
		item->model = SG_ModelIndex("progs/g_rock.mdl");
	else if (!Str_ICompare(name, "weapon_rocketlauncher"))
		item->model = SG_ModelIndex("progs/g_rock2.mdl");
	else if (!Str_ICompare(name, "weapon_lightning"))
		item->model = SG_ModelIndex("progs/g_light.mdl");
}

/*
 ==================

 FIXME: use the spawning array of entities to load the models?

 FIXME: this bounds needs to be set per model
 ==================
*/
void etEntity::ParseMonsterState (){

	monster_t	*monster = (monster_t *)state.buffer;
	const char	*name;

	state.type = ET_MONSTER;

	// Get the bounds
	bounds.Set(-8.0f, -8.0f, 0.0f, 8.0f, 8.0f, 16.0f);

	monster->bounds = G_EncodeBounds(bounds);

	// Set the model
	name = spawnDict.GetString("className");

	if (!Str_ICompare(name, "monster_army"))
		monster->model = SG_ModelIndex("progs/soldier.mdl");
	else if (!Str_ICompare(name, "monster_enforcer"))
		monster->model = SG_ModelIndex("progs/enforcer.mdl");
	else if (!Str_ICompare(name, "monster_dog"))
		monster->model = SG_ModelIndex("progs/dog.mdl");
	else if (!Str_ICompare(name, "monster_ogre"))
		monster->model = SG_ModelIndex("progs/ogre.mdl");
	else if (!Str_ICompare(name, "monster_knight"))
		monster->model = SG_ModelIndex("progs/knight.mdl");
	else if (!Str_ICompare(name, "monster_zombie"))
		monster->model = SG_ModelIndex("progs/zombie.mdl");
	else if (!Str_ICompare(name, "monster_wizard"))
		monster->model = SG_ModelIndex("progs/wizard.mdl");
	else if (!Str_ICompare(name, "monster_demon1"))
		monster->model = SG_ModelIndex("progs/demon.mdl");
	else if (!Str_ICompare(name, "monster_shambler"))
		monster->model = SG_ModelIndex("progs/shambler.mdl");
}


/*
 ==============================================================================

 BINDING

 ==============================================================================
*/


/*
 ==================
 etEntity::BindToMaster
 ==================
*/
bool etEntity::BindToMaster (etEntity *master, bool orientated){

	if (master == this){
		G_Printf(S_COLOR_YELLOW "Cannot bind '%s' to '%s' (bind to self)\n", name, master->name);
		return false;
	}

	if (master->IsBoundTo(this)){
		G_Printf(S_COLOR_YELLOW "Cannot bind '%s' to '%s' (recursive bind)\n", name, master->name);
		return false;
	}

	// Bind to master
	state.bindMaster = master->state.number;
	state.bindOrientated = orientated;

	return false;
}

/*
 ==================
 etEntity::IsBound
 ==================
*/
bool etEntity::IsBound () const {

	if (state.bindMaster == ENTITYNUM_NONE)
		return false;

	return true;
}

/*
 ==================
 etEntity::IsBoundTo
 ==================
*/
bool etEntity::IsBoundTo (etEntity *master) const {

	const etEntity	*check;

	if (state.bindMaster == ENTITYNUM_NONE)
		return false;

	check = this;

	while (check->state.bindMaster != ENTITYNUM_NONE){
		check = &level.entities[check->state.bindMaster];

		if (check == master)
			return true;
	}

	return false;
}

/*
 ==================
 etEntity::GetBindMaster
 ==================
*/
etEntity *etEntity::GetBindMaster () const {

	if (state.bindMaster == ENTITYNUM_NONE)
		return NULL;

	return &level.entities[state.bindMaster];
}

/*
 ==================
 etEntity::GetBindOrientated
 ==================
*/
bool etEntity::GetBindOrientated () const {

	if (state.bindMaster == ENTITYNUM_NONE)
		return false;

	return state.bindOrientated;
}

/*
 ==================
 etEntity::Bind
 ==================
*/
void etEntity::Bind (){

	etEntity	*master;
	const char	*bind;

	// Unbind from current master
	Unbind();

	// Bind to another entity if desired
	if (!spawnDict.GetString("bind", &bind))
		return;

	// Find the new master
	master = SG_FindEntity(bind);
	if (!master){
		G_Printf(S_COLOR_YELLOW "Cannot bind '%s' to '%s' (not found)\n", name, bind);
		return;
	}

	// Bind it to the new master
	if (!BindToMaster(master, spawnDict.GetBool("bindOrientated")))
		return;

	// We are now separated from our previous team and are either an individual
	// or have a team of our own. Now we can join the new master's team.
	JoinTeam(master);
}

/*
 ==================
 etEntity::Bind
 ==================
*/
void etEntity::Bind (etEntity *master, bool orientated){

	// Unbind from current master
	Unbind();

	// Bind to another entity if desired
	if (!master)
		return;

	// Bind it to the new master
	if (!BindToMaster(master, orientated))
		return;

	// We are now separated from our previous team and are either an individual
	// or have a team of our own. Now we can join the new master's team.
	JoinTeam(master);
}

/*
 ==================
 etEntity::Unbind
 ==================
*/
void etEntity::Unbind (){

	etEntity	*entity, *last;
	etEntity	*prev, *next;

	if (state.bindMaster == ENTITYNUM_NONE)
		return;

	if (!teamMaster){
		// Unbind from master
		state.bindMaster = ENTITYNUM_NONE;
		state.bindOrientated = false;

		return;
	}

	// We're still part of a team, so that means I have to extricate myself and
	// any entities that are bound to me from the old team.
	// Find the node previous to me in the team.
	prev = teamMaster;

	for (entity = teamMaster->teamChain; entity && entity != this; entity = entity->teamChain)
		prev = entity;

	// Find the last node in my team that is bound to me.
	// Also find the first node not bound to me, if one exists.
	last = this;

	for (next = teamChain; next; next = next->teamChain){
		if (!next->IsBoundTo(this))
			break;

		// Tell them I'm now the team master
		next->teamMaster = this;
		last = next;
	}

	// Disconnect the last member of our team from the old team
	last->teamChain = NULL;

	// Connect up the previous member of the old team to the node that follows
	// the last node bound to me, if one exists
	if (teamMaster != this){
		prev->teamChain = next;

		if (!next && teamMaster == prev)
			prev->teamMaster = NULL;
	}
	else if (next){
		// If we were the team master, then the nodes that were not bound to me
		// are now a disconnected chain. Make them into their own team.
		for (entity = next; entity->teamChain; entity = entity->teamChain)
			entity->teamMaster = next;

		next->teamMaster = next;
	}

	// If we don't have anyone on our team, then clear the team variables
	if (teamChain)
		teamMaster = this;
	else
		teamMaster = NULL;

	// Unbind from master
	state.bindMaster = ENTITYNUM_NONE;
	state.bindOrientated = false;
}


/*
 ==============================================================================

 TEAMS

 ==============================================================================
*/


/*
 ==================
 etEntity::JoinTeam
 ==================
*/
void etEntity::JoinTeam (etEntity *teamMember){

	etEntity	*master, *entity;
	etEntity	*prev, *next;

	// If we're already on a team, quit it so we can join this one
	if (teamMaster && teamMaster != this)
		QuitTeam();

	if (teamMember == this){
		teamMaster = this;
		return;
	}

	// Check if our new team mate is already on a team
	master = teamMember->teamMaster;
	if (!master){
		// He's not on a team, so he's the new team master
		teamMaster = teamMember;

		teamMember->teamMaster = teamMember;
		teamMember->teamChain = this;

		// Make anyone who's bound to me part of the new team
		for (entity = teamChain; entity; entity = entity->teamChain)
			entity->teamMaster = teamMember;

		return;
	}

	teamMaster = master;

	// Skip past the chain members bound to the entity we're teaming up with
	prev = teamMember;
	next = teamMember->teamChain;

	if (state.bindMaster == ENTITYNUM_NONE){
		// If we're not bound to someone, then put us at the end of the team
		while (next){
			prev = next;
			next = next->teamChain;
		}
	}
	else {
		// If we have a bind master, join after any entities bound to the
		// entity we're joining
		while (next && next->IsBoundTo(teamMember)){
			prev = next;
			next = next->teamChain;
		}
	}

	// Make anyone who's bound to me part of the new team and also find the
	// last member of my team
	for (entity = this; entity->teamChain; entity = entity->teamChain)
		entity->teamChain->teamMaster = master;

	prev->teamChain = this;
	entity->teamChain = next;
}

/*
 ==================
 etEntity::QuitTeam
 ==================
*/
void etEntity::QuitTeam (){

	etEntity	*entity;

	if (!teamMaster)
		return;

	// Check if I'm the team master
	if (teamMaster == this){
		// Do we have more than one team mate?
		if (!teamChain->teamChain){
			// No, break up the team
			teamChain->teamMaster = NULL;
		}
		else {
			// Yes, so make the first team mate the team master
			for (entity = teamChain; entity; entity = entity->teamChain)
				entity->teamMaster = teamChain;
		}

		teamMaster = NULL;
		teamChain = NULL;

		return;
	}

	// Find the previous member of the team chain
	entity = teamMaster;

	while (entity->teamChain != this)
		entity = entity->teamChain;

	// Remove this from the team chain
	entity->teamChain = teamChain;

	// If no one is left on the team, break it up
	if (!teamMaster->teamChain)
		teamMaster->teamMaster = NULL;

	teamMaster = NULL;
	teamChain = NULL;
}

/*
 ==================
 etEntity::JoinMoverTeam
 ==================
*/
void etEntity::JoinMoverTeam (etEntity *master){

	moveChain = master->moveChain;
	master->moveChain = this;
}

/*
 ==================
 etEntity::QuitMoverTeam
 ==================
*/
void etEntity::QuitMoverTeam (){

	etEntity	*mover;

	if (!moveMaster)
		return;

	// If this is the move master
	if (moveMaster == this){
		// Make the next mover in the chain the move master
		for (mover = moveMaster; mover; mover = mover->moveChain)
			mover->moveMaster = moveChain;

		moveMaster = NULL;
		moveChain = NULL;

		return;
	}

	// Remove mover from the chain
	for (mover = moveMaster; mover; mover = mover->moveChain){
		if (mover->moveChain == this){
			mover->moveChain = moveChain;
			break;
		}
	}

	moveMaster = NULL;
	moveChain = NULL;
}


/*
 ==============================================================================

 MATERIAL PARMS

 ==============================================================================
*/


/*
 ==================
 etEntity::ParseMaterialParms
 ==================
*/
void etEntity::ParseMaterialParms (float *parms){

	glqVec3	color;
	float	alpha;

	materialParms = parms;

	// Get the color
	if (spawnDict.GetVec3("color", color, "1 1 1")){
		materialParms[0] = baseColor[0] = color[0];
		materialParms[1] = baseColor[1] = color[1];
		materialParms[2] = baseColor[2] = color[2];
	}
	else {
		materialParms[0] = baseColor[0] = spawnDict.GetFloat("materialParm0", "1");
		materialParms[1] = baseColor[1] = spawnDict.GetFloat("materialParm1", "1");
		materialParms[2] = baseColor[2] = spawnDict.GetFloat("materialParm2", "1");
	}

	// Get the alpha
	if (spawnDict.GetFloat("alpha", &alpha, "1"))
		materialParms[3] = baseColor[3] = alpha;
	else
		materialParms[3] = baseColor[3] = spawnDict.GetFloat("materialParm3", "1");

	// Get the material parms
	materialParms[4] = spawnDict.GetFloat("materialParm4");
	materialParms[5] = spawnDict.GetFloat("materialParm5");
	materialParms[6] = spawnDict.GetFloat("materialParm6");
	materialParms[7] = spawnDict.GetFloat("materialParm7");
}

/*
 ==================
 etEntity::SetMaterialParm
 ==================
*/
void etEntity::SetMaterialParm (int index, float value){

	if (!materialParms)
		return;

	if (index < 0 || index >= MAX_MATERIAL_PARMS)
		G_Error("odEntity::SetMaterialParm: index out of range");

	materialParms[index] = value;
}


/*
 ==============================================================================

 SOUND PARMS

 ==============================================================================
*/


/*
 ==================
 etEntity::ParseSoundParms
 ==================
*/
void etEntity::ParseSoundParms (float *parms){

	glqVec2	filter;
	float	value;

	soundParms = parms;

	// Get the volume
	if (spawnDict.GetFloat("volume", &value, "1"))
		soundParms[0] = value;
	else
		soundParms[0] = spawnDict.GetFloat("soundParm0", "1");

	soundParms[0] = glqMath::ClampFloat(soundParms[0], 0.0f, 1.0f);

	// Get the pitch
	if (spawnDict.GetFloat("pitch", &value, "1"))
		soundParms[1] = value;
	else
		soundParms[1] = spawnDict.GetFloat("soundParm1", "1");

	soundParms[1] = glqMath::ClampFloat(soundParms[1], 0.1f, 10.0f);

	// Get the dry filter
	if (spawnDict.GetVec2("dryFilter", filter, "1 1")){
		soundParms[2] = filter[0];
		soundParms[3] = filter[1];
	}
	else {
		soundParms[2] = spawnDict.GetFloat("soundParm2", "1");
		soundParms[3] = spawnDict.GetFloat("soundParm3", "1");
	}

	soundParms[2] = glqMath::ClampFloat(soundParms[2], 0.0f, 1.0f);
	soundParms[3] = glqMath::ClampFloat(soundParms[3], 0.0f, 1.0f);

	// Get the wet filter
	if (spawnDict.GetVec2("wetFilter", filter, "1 1")){
		soundParms[4] = filter[0];
		soundParms[5] = filter[1];
	}
	else {
		soundParms[4] = spawnDict.GetFloat("soundParm4", "1");
		soundParms[5] = spawnDict.GetFloat("soundParm5", "1");
	}

	soundParms[4] = glqMath::ClampFloat(soundParms[4], 0.0f, 1.0f);
	soundParms[5] = glqMath::ClampFloat(soundParms[5], 0.0f, 1.0f);

	// Get the min distance
	if (spawnDict.GetFloat("minDistance", &value))
		soundParms[6] = value;
	else
		soundParms[6] = spawnDict.GetFloat("soundParm6");

	soundParms[6] = Max(soundParms[6], 0.0f);

	// Get the max distance
	if (spawnDict.GetFloat("maxDistance", &value))
		soundParms[7] = value;
	else
		soundParms[7] = spawnDict.GetFloat("soundParm7");

	soundParms[7] = Max(soundParms[7], 0.0f);
}


/*
 ==============================================================================

 TRAJECTORIES

 ==============================================================================
*/


/*
 ==================
 etEntity::SetOrigin
 ==================
*/
void etEntity::SetOrigin (const glqVec3 &origin){

	state.origin.Init(TR_STATIONARY, 0, 0, origin, vec3_origin);
}

/*
 ==================
 etEntity::SetOriginInterpolate
 ==================
*/
void etEntity::SetOriginInterpolate (const glqVec3 &origin){

	state.origin.Init(TR_INTERPOLATE, 0, 0, origin, vec3_origin);
}

/*
 ==================
 etEntity::SetAngles
 ==================
*/
void etEntity::SetAngles (const glqAngles &angles){

	state.angles.Init(TR_STATIONARY, 0, 0, angles, angles_zero);
}

/*
 ==================
 etEntity::SetAnglesInterpolate
 ==================
*/
void etEntity::SetAnglesInterpolate (const glqAngles &angles){

	state.angles.Init(TR_INTERPOLATE, 0, 0, angles, angles_zero);
}

/*
 ==================
 etEntity::GetOrigin
 ==================
*/
glqVec3 etEntity::GetOrigin () const {

	if (state.origin.type == TR_STATIONARY)
		return state.origin.base;

	return state.origin.Evaluate(level.time, sg_gravity->floatValue);
}

/*
 ==================
 etEntity::GetAngles
 ==================
*/
glqAngles etEntity::GetAngles () const {

	if (state.angles.type == TR_STATIONARY)
		return state.angles.base;

	return state.angles.Evaluate(level.time, sg_gravity->floatValue);
}


/*
 ==============================================================================

 CLIPPING INFORMATION

 ==============================================================================
*/


/*
 ==================

 ==================
*/
void etEntity::SetClipModel (clipModel_t *clipModel, int contents, const glqBounds &bounds){

}


/*
 ==============================================================================

 PHYSICS

 ==============================================================================
*/


/*
 ==================
 etEntity::EnablePhysics
 ==================
*/
void etEntity::EnablePhysics (){

	// Enable physics on the team master if this entity is part of a physics
	// team
	if (teamMaster && teamMaster != this){
		teamMaster->flags |= FL_PHYSICS;

		// Link the team master
		SG_LinkEntity(teamMaster);
	}

	// Enable physics
	flags |= FL_PHYSICS;

	// Link the entity
	SG_LinkEntity(this);
}

/*
 ==================
 etEntity::DisablePhysics
 ==================
*/
void etEntity::DisablePhysics (){

	etEntity	*entity;

	// May only disable physics on a team master if no team members are running
	// physics
	if (teamMaster && teamMaster == this){
		for (entity = teamChain; entity; entity = entity->teamChain){
			if (entity->flags & FL_PHYSICS)
				return;
		}
	}

	// Disable physics
	flags &= ~FL_PHYSICS;

	// Unlink the entity
	SG_UnlinkEntity(this);
}

/*
 ==================

 Returns true if the entity has been moved, rotated, or resized
 ==================
*/
bool etEntity::EvaluatePhysics (){

	return true;
}

/*
 ==================

 Runs physics and updates the current position and orientation if necessary
 ==================
*/
void etEntity::RunPhysics (){

}


/*
 ==============================================================================

 THINKING

 ==============================================================================
*/


/*
 ==================
 etEntity::RunThink

 Runs thinking code for this frame if necessary
 ==================
*/
void etEntity::RunThink (){

	if (!nextThinkTime)
		return;

	if (nextThinkTime > level.time)
		return;

	nextThinkTime = 0;

	if (!Think)
		G_Error("odEntity::RunThink: entity '%s' doesn't have a think function", name);

	Think(this);
}


/*
 ==============================================================================

 ANIMATIONS

 ==============================================================================
*/


/*
 ==================

 ==================
*/
void etEntity::UpdateAnimation (){

	if (!animator)
		return;

	// If hidden, no need to update
	if (flags & FL_HIDDEN)
		return;

	// If not animating or forced to a fixed frame, no need to update
}


/*
 ==============================================================================

 MISCELLANEOUS FUNCTIONS

 ==============================================================================
*/


/*
 ==================
 etEntity::IsHidden
 ==================
*/
bool etEntity::IsHidden () const {

	return (flags & FL_HIDDEN);
}

/*
 ==================
 etEntity::IsEnabled
 ==================
*/
bool etEntity::IsEnabled () const {

	return (flags & FL_ENABLED);
}

/*
 ==================
 etEntity::Hide
 ==================
*/
void etEntity::Hide (){

	if (flags & FL_HIDDEN)
		return;

	flags |= FL_HIDDEN;

	// Call the hidden function
	if (Hidden)
		Hidden(this);
}

/*
 ==================
 etEntity::Show
 ==================
*/
void etEntity::Show (){

	if (!(flags & FL_HIDDEN))
		return;

	flags &= ~FL_HIDDEN;

	// Call the shown function
	if (Shown)
		Shown(this);
}

/*
 ==================
 etEntity::Enable
 ==================
*/
void etEntity::Enable (){

	if (flags & FL_ENABLED)
		return;

	flags |= FL_ENABLED;

	// Call the enabled function
	if (Enabled)
		Enabled(this);
}

/*
 ==================
 etEntity::SetSort
 ==================
*/
void etEntity::SetSort (){

	etEntity	*master;
	int			depth;

	// Make sure players always come first
	if (state.type == ET_PLAYER){
		sort = 0;
		return;
	}

	depth = 1;

	// If bound to another entity
	if (state.bindMaster != ENTITYNUM_NONE){
		master = this;

		while (master->state.bindMaster != ENTITYNUM_NONE){
			master = &level.entities[master->state.bindMaster];

			depth++;
		}
	}

	// Make sure certain entities always come after their targets
	if (flags & FL_CONVEYOR)
		sort = (3 << 16) | depth;
	else if (state.type == ET_BEAM)
		sort = (2 << 16) | depth;
	else
		sort = (1 << 16) | depth;
}

/*
 ==================
 etEntity::AddEvent
 ==================
*/
void etEntity::AddEvent (int eventType, int eventParm){

	int		index;

	if (client){
		client->AddEvent(eventType, eventParm);
		return;
	}

	index = state.eventSequence & (MAX_EVENTS - 1);

	state.eventSequence++;
	state.eventTypes[index] = eventType;
	state.eventParms[index] = eventParm;
}

/*
 ==================
 etEntity::Teleport
 ==================
*/
void etEntity::Teleport (const glqVec3 &origin, const glqAngles &angles, bool keepMomentum){

	if (client){
		client->Teleport(origin, angles, keepMomentum);
		return;
	}

	// Only allow teleporting physics objects
	if (!physics)
		return;

	physics->groundEntityNum = ENTITYNUM_NONE;

	// Clear the velocity if desired
	if (!keepMomentum)
		physics->velocity.Clear();

	// Toggle the teleport flag so the client knows to not interpolate
	flags ^= FL_TELEPORT;

	// Update position and orientation
	currentOrigin = origin;
	currentAngles = angles;
	currentAxis = angles.ToMat3();

	// Clear the ground entity
	groundEntity = NULL;

	// Update the trajectories
	SetOriginInterpolate(origin);
	SetAnglesInterpolate(angles);

	// Link the entity
	SG_LinkEntity(this);

	// Kill anything at the destination
	SG_KillBox(this);
}

/*
 ==================

 Returns true if the inflictor can directly damage this entity. Used for
 explosions and melee attacks.
 ==================
*/
bool etEntity::CanDamage (){

	return false;
}

/*
 ==================

 inflictor		Entity that is causing the damage
 attacker		Entity that caused the inflictor to damage this entity
 point			Point at which the damage is being inflicted (for headshots)
 direction		Direction of the attack for knockback
 damage			Amount of damage being inflicted
 damageFlags	Flags used to control how this function works
 meansOfDeath	Means of death for obituaries

 Example: inflictor = rocket, attacker = player

 Damage flags:

	DAMAGE_RADIUS			Damage was indirect (from a nearby explosion)
	DAMAGE_NO_ARMOR			Armor does not protect from this damage
	DAMAGE_NO_KNOCKBACK		Do not affect velocity, just view angles
	DAMAGE_NO_PROTECTION	Kills godmode, armor, everything

 Both inflictor and attacker can be NULL for environmental effects.
 ==================
*/
void etEntity::Damage (etEntity *inflictor, etEntity *attacker, const glqVec3 &point, const glqVec3 &direction, int damage, int damageFlags, meansOfDeath_t meansOfDeath){

	if (damage <= 0)
		return;

	if (!(flags & FL_DAMAGEABLE))
		return;

	if (!inflictor)
		inflictor = level.worldEntity;

	if (!attacker)
		attacker = level.worldEntity;

	// Doors and buttons can be damaged but they do not use any health
	if (inflictor->state.flags & ET_MOVER){

		// TODO: call their "Use" function

		return;
	}

	// TODO: check for super damage, and multiply it by 4

	// Determine knockback

	// Figure momentum add

	// Check for completely getting out of the damage
	if (!(damageFlags & DAMAGE_NO_PROTECTION)){
		// Check for godmode
		if (client && client->godMode)
			return;

		// Check for invincibility
	}

	// TODO: reduce armor if any

	// TODO: view blends stuff

	// Do the damage
	if (damage <= 0)
		return;

	health -= damage;

	if (health > 0){
		// Call the pain function
		if (client)
			client->Pain(inflictor, attacker, point, direction, damage);
		else if (Pain)
			Pain(this, inflictor, attacker, point, direction, damage);
	}
	else {
		if (health < -999)
			health = -999;

		// Call the kill function
		if (client)
			client->Kill(inflictor, attacker, point, direction, damage, meansOfDeath);
		else if (Kill)
			Kill(this, inflictor, attacker, point, direction, damage, meansOfDeath);
	}
}